﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;

#region IMLibrary
using IMLibrary.NetClient;
using IMLibrary.Security;
using IMLibrary.IO;
using IMLibrary.BinaryPacket;
using IMLibrary.Net.Enum;
#endregion

namespace Ourmsg.FileTransmit
{
    /// <summary>
    /// 文件传输基础类
    /// </summary>
    public class P2PFileTransmitBaseV1 : P2PClientV1  
    {
        #region 构造
        /// <summary>
        /// 
        /// </summary>
        /// <param name="tFileInfo"></param>
        /// <param name="serverIP"></param>
        /// <param name="Port"></param>
        /// <param name="isSend"></param>
        public P2PFileTransmitBaseV1(TFileInfo tFileInfo , string serverIP, int Port,bool isSend)
            : base(serverIP, Port, isSend, NetDelivery.ReliableOrdered)
        {
            _IsSend = isSend;
            TFileInfo = tFileInfo;
            if (!isSend)
            {
                ///创建接收文件夹
                System.IO.DirectoryInfo dInfo = new System.IO.DirectoryInfo("ReceivedFile");
                if (!dInfo.Exists)
                    dInfo.Create();
                dInfo = null;
                ///创建接收缓存文件夹
                dInfo = new System.IO.DirectoryInfo("FileCache");
                if (!dInfo.Exists)
                    dInfo.Create();
                dInfo = null;
                CacheFile = "FileCache\\" + TFileInfo.MD5;
            }
            IniEvents();// 初始化数据连接事件
        }

        #endregion

        #region 释放资源
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            CloseTimer(); 
            Stop();//关闭套接字 
            CloseFileStream();// 关闭文件流
        } 
        #endregion

        #region 变量
        /// <summary>
        /// 当前获得文件的数据长度
        /// </summary>
        protected long currGetPos = 0;
        /// <summary>
        /// 传输文件的信息
        /// </summary>
        public TFileInfo TFileInfo = new TFileInfo();
        /// <summary>
        /// 缓冲文件保存位置
        /// </summary>
        protected string CacheFile = "";
        /// <summary>
        /// 文件操作流
        /// </summary>
        protected FileStream FS = null; 
        /// <summary>
        /// 要发送的缓冲区
        /// </summary>
        protected byte[] buffer = null;  
        /// <summary>
        /// 心跳时钟
        /// </summary>
        System.Timers.Timer timer;
        /// <summary>
        /// 是否收到发送请求
        /// </summary>
        long currLength = 0;

        int RetryCount = 0;
        #endregion

        #region 属性
        bool _IsSend = false;
        /// <summary>
        /// 标记本机是发送端还是接收端
        /// </summary>
        public bool IsSend
        {
            get { return _IsSend; }
        }
        #endregion

        #region 事件
        /// <summary>
        /// 文件传输结束
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public delegate void fileTransmitEventHandler(object sender, fileTransmitEvnetArgs e);
        /// <summary>
        /// 离线文件传输事件
        /// </summary>
        public event fileTransmitEventHandler OfflineFile;
        /// <summary>
        /// 触发离线文件传输事件
        /// </summary>
        public void OnOfflineFile()
        {
            if (OfflineFile != null)
                OfflineFile(this, new fileTransmitEvnetArgs(TFileInfo));
        } 
        /// <summary>
        /// 文件传输结束
        /// </summary>
        public event fileTransmitEventHandler  Transmitted;
        /// <summary>
        /// 触发文件传输结束事件
        /// </summary>
        protected virtual void OnFileTransmitted()
        {
            if (Transmitted != null)
                Transmitted(this, new fileTransmitEvnetArgs(TFileInfo));
        }  
        /// <summary>
        /// 允许断点续传
        /// </summary>
        public event fileTransmitEventHandler AllowResume;
        /// <summary>
        /// 触发允许文件断点续传事件
        /// </summary>
        protected virtual void OnFileAllowResume()
        {
            if (AllowResume != null)
                AllowResume(this, new fileTransmitEvnetArgs(TFileInfo));
        } 
        /// <summary>
        /// 取消文件传输
        /// </summary>
        public event fileTransmitEventHandler Cancel;
        /// <summary>
        /// 触发文件传输取消事件
        /// </summary>
        protected virtual void OnFileTransmitCancel()
        {
            if (Cancel != null)
                Cancel(this, new fileTransmitEvnetArgs(TFileInfo));
        } 

        /// <summary>
        /// 文件传输错误事件
        /// </summary>
        public event fileTransmitEventHandler  Error;
        /// <summary>
        /// 触发文件传输错误事件
        /// </summary>
        protected virtual void OnFileTransmitError()
        {
            if (Error != null)
                Error(this, new fileTransmitEvnetArgs(TFileInfo));
        } 
        /// <summary>
        /// 事件：发送或收到文件数据
        /// </summary>
        public event fileTransmitEventHandler  Transmitting;
        /// <summary>
        /// 触发文件传输中事件
        /// </summary>
        protected virtual void OnFileTransmitting()
        {
            if (Transmitting != null)
            {
                TFileInfo.CurrLength = currGetPos;//获得当前已经传输的文件尺寸
                Transmitting(this, new fileTransmitEvnetArgs(TFileInfo));
            }
        }   
        #endregion

        #region 初始化连接事件
        /// <summary>
        /// 初始化数据连接事件
        /// </summary>
        private void IniEvents()
        {
            #region 收到数据事件
            RecivedData += (sender, e) =>
            {
                if (!timer.Enabled)
                    timer.Enabled = true;

                ReceiveData(e.Data);//执行收到数据事件
            };
            #endregion

            #region 网络成功联接事件
            Connected += (s, e) =>
            {
                if (!timer.Enabled)
                    timer.Enabled = true;

                if (!IsSend)//请求发送文件
                    RequestSendFilePak(false);
            };
            #endregion

            #region 心跳检测事件
            //创建心跳为1秒的时钟 
            timer = new System.Timers.Timer(1000);
            timer.Elapsed += (s, e) =>
            {
                RetryCount++;
                if (RetryCount == 6)
                {
                    if (IsSend && currGetPos >= TFileInfo.Length)
                    {
                        EndSendFile();
                        return;
                    } 

                    if (currLength == currGetPos)
                        RequestSendFilePak(false);

                    RetryCount = 0;
                }

                if (currLength != currGetPos)
                {
                    currLength = currGetPos;
                    RetryCount = 0;
                }

                OnFileTransmitting();//触发文件传输中事件  
            };
            #endregion
        }
        #endregion

        #region 关闭心跳时钟
        /// <summary>
        /// 关闭心跳时钟
        /// </summary>
        private void CloseTimer()
        {
            if (timer != null)
            {
                timer.Enabled = false; timer.Close(); timer.Dispose(); timer = null;
            } 
        }
        #endregion

        #region 关闭文件流
        /// <summary>
        /// 关闭文件流
        /// </summary>
        private void CloseFileStream()
        {
            if (FS != null) 
            { FS.Close(); FS.Dispose(); FS = null; }
        }
        #endregion

        #region 收到数据包事件
        private void ReceiveData(byte[] data)
        {
            if (data.Length < FilePacket.HeaderLength) return;
            FilePacket filePacket = new FilePacket(data);//转换为协议对像

            if (filePacket.CommandType == (byte)TransmitType.SendFile)//收到文件发送请求
            {
                if (IsSend)//如果是文件发送者
                {
                    if (data.Length == 9)
                    {
                        FlushSendQueue();//清空发送缓冲区
                        long offset = BitConverter.ToInt64(filePacket.Payload, 0);//获取发送文件起始位置
                        sendFile(offset); //则按指定位置发送文件
                    }
                    else
                        sendFile(); //则按指定位置发送文件
                }
            }
            else if (filePacket.CommandType == (byte)TransmitType.getFilePackage)//收到文件数据包 
            {
                if (!IsSend)// 如果是文件接收者，处理接收到的文件数据包
                    SaveFileData(filePacket.Payload);
            }
        }
        #endregion

        #region 结束文件发送判断
        /// <summary>
        /// 结束文件发送判断
        /// </summary>
        private void EndSendFile()
        {
            #region 如果对方要求发送的数据块起始位置大于文件尺寸则认为是非法请求退出
            if (currGetPos >= TFileInfo.Length)
            {
                CloseFileStream();
                CloseTimer();
                OnFileTransmitted();//触发文件传输结束事件
                Stop();//关闭套接字 
                return;
            }
            #endregion
        }
        #endregion

        #region 发送文件
        private void sendFile(long offset)
        {
            EndSendFile();
             
            if (FS == null)//打开文件
            {
                FS = new FileStream(TFileInfo.fullName, FileMode.Open, FileAccess.Read, FileShare.Read);
            }
            FS.Seek(offset, SeekOrigin.Begin);
            currGetPos = offset;
            sendFile();
        }

        /// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="requestLastPos">当前已经传输完成的文件数据长度</param>
        /// <param name="isSelf">是否对方调用</param>
        private void sendFile( )
        {
            EndSendFile();

            if (buffer == null)
                buffer = new byte[MTU];

            if (currGetPos + MTU > TFileInfo.Length)
                buffer = new byte[TFileInfo.Length - currGetPos];

            ///读文件到缓冲区
            FS.Read(buffer, 0, buffer.Length);

            FilePacket filePacket = new FilePacket();//转换为协议对像
            filePacket.CommandType = (byte)TransmitType.getFilePackage;
            filePacket.Payload = buffer;
            SendConnectData(filePacket.ToArray());

            currGetPos += buffer.Length; 
        }
        #endregion

        #region 保存文件数据
        /// <summary>
        /// 保存文件数据
        /// </summary>
        private void SaveFileData(byte[] data)
        {
            if (FS == null)//打开文件
                FS = new FileStream(CacheFile, FileMode.Append, FileAccess.Write, FileShare.Read);

            FS.Write(data, 0, data.Length);

            currGetPos += data.Length;

            RequestSendFilePak(true);

            #region 如果文件接收完全，触发事件
            if (currGetPos >= TFileInfo.Length)
            {
                CloseFileStream();
                Console.WriteLine("文件接收完成." + TFileInfo.fullName);
                if (File.Exists(TFileInfo.fullName))
                {
                    File.Delete(TFileInfo.fullName);
                    System.Threading.Thread.Sleep(500);
                }

                if (File.Exists(CacheFile))
                    File.Move(CacheFile, TFileInfo.fullName);//拷贝文件

                OnFileTransmitted();//触发结束事件
                Dispose();//关闭套接字资源
            }  
            #endregion
        }
        #endregion
         
        #region  请求对方发送文件数据包
        /// <summary>
        /// 请求对方发送文件数据包
        /// </summary>
        private void RequestSendFilePak(bool Next)
        {
            //Console.WriteLine("请求对方发送文件!" + currGetPos.ToString());
            FilePacket RequestSendFilePakMsg = new FilePacket();
            RequestSendFilePakMsg.CommandType = (byte)TransmitType.SendFile;//请求发送文件
            RequestSendFilePakMsg.Payload = BitConverter.GetBytes(currGetPos);//当前要获取数据据的偏移量
            if (Next)
                SendConnectData(RequestSendFilePakMsg.Header);//请求对方发送文件数据
            else
                SendConnectData(RequestSendFilePakMsg.ToArray());//请求对方发送文件数据   
        }
        #endregion

        #region 公共方法

        #region 开始文件传输
        /// <summary>
        /// 开始文件传输
        /// </summary>
        /// <param name="AllowResume">是否断点续传文件</param>
        public void StartFileTransmit(bool AllowResume)
        {
            #region 文件传输准备工作
            System.IO.DirectoryInfo dInfo = new System.IO.DirectoryInfo("FileCache");
            if (!dInfo.Exists)
                dInfo.Create();
            CacheFile = "FileCache\\" + TFileInfo.MD5;

            if (AllowResume)//如果断点续传
            {
                FileInfo finfo = new FileInfo(CacheFile);
                if (finfo.Exists)//如果缓存文件存在，则触发可断点续传事件
                    currGetPos = finfo.Length;//设置断点续传接收文件位置
            }
            else
            {
                File.Delete(CacheFile);//删除缓存文件
                System.Threading.Thread.Sleep(500);
            }
            #endregion

            this.Start();//开始网络连接
        }
        #endregion

        #region 取消文件传输
        /// <summary>
        /// 取消文件传输
        /// </summary>
        public void CancelTransmit()
        {
            CloseTimer();
            Stop();//关闭套接字 
            CloseFileStream();// 关闭文件流
        }
        #endregion

        #endregion 
    }
}
